﻿using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace Actor.Net.Network.Actor
{
    public enum NetServiceType
    {
        Undefined = 0,
        Client = 1,
        Server = 2,
    }

    /// <summary>
    /// 网络服务基类
    /// </summary>
    public abstract class ActorBaseService : IDisposable
    {
        public ActorNetwork Network { get; }
        public ActorBaseChannel ConnectChannel { get; set; }
        public NetServiceType ServiceType { get; set; }
        public Action<ActorChannelContext> OnConnected { get; set; }
        public Action<ActorChannelContext> OnDisconnected { get; set; }
        public Action<ActorChannelContext, Exception> OnChannelError { get; set; }
        public Action<ActorChannelContext> OnReconnectFailed { get; set; }
        public ConcurrentDictionary<int, ActorChannelContext> ConnectingChannels { get; } = new ConcurrentDictionary<int, ActorChannelContext>();
        public static ConcurrentDictionary<int, ActorChannelContext> ChannelContexts { get; } = new ConcurrentDictionary<int, ActorChannelContext>();

        public ActorBaseService(ActorNetwork network)
        {
            this.Network = network;
        }

        public abstract ActorChannelContext Connect(int timeoutMillisecond);
        public abstract Task<ActorChannelContext> ConnectAsync(CancellationToken cancellationToken);
        public abstract void Listen();

        protected int CreateChannelId()
        {
            int id = ActorChannelIdCreator.CreateId();
            while (true)
            {
                if (ActorBaseService.ChannelContexts.ContainsKey(id))
                    id = ActorChannelIdCreator.CreateId();
                else
                    break;
            }
            return id;
        }

        public virtual void Update() { }

        internal virtual void ProcessConnected(ActorChannelContext context)
        {
            if (ActorBaseService.ChannelContexts.ContainsKey(context.ChannelId))
                return;

            if (!context.Channel.Connected)
                return;

            ActorBaseService.ChannelContexts.AddOrUpdate(context.ChannelId, context, (k, v) => context);
            try
            {
                this.OnConnected?.Invoke(context);
            }
            catch(Exception ex)
            {
                this.ProcessSocketError(context, ex);
            }
        }

        internal virtual void ProcessDisconnected(ActorChannelContext context)
        {
            if (!ActorBaseService.ChannelContexts.TryRemove(context.ChannelId, out context))
                return;

            if (this.ServiceType == NetServiceType.Client)
            {
                try
                {
                    this.OnDisconnected?.Invoke(context);
                }
                catch(Exception ex)
                {
                    this.ProcessSocketError(context, ex);
                }
                return;
            }

            try
            {
                this.OnDisconnected?.Invoke(context);
            }
            catch(Exception ex)
            {
                this.ProcessSocketError(context, ex);
            }
        }

        internal void ProcessSocketError(ActorChannelContext context, Exception exception)
        {
            try
            {
                this.OnChannelError?.Invoke(context, exception);
            }
            catch{ }
        }

        public virtual void Dispose()
        {
            foreach(var channel in ConnectingChannels.Values)
            {
                channel.Channel.Disconnect();
            }
        }
    }
}
